/**
 * @file dhcp_cc2530.c
 * @brief Implements DHCP related functions, specific to the CC2530 chip in 
 *        the wizi project.
 * @author Vamshi Ponnapalli
 */
/*******************************************************************************
 * Includes
 ******************************************************************************/
#include "common.h"
#include "network.h"
#include "dhcp.h"
#include "dhcp_wizi.h"
#include <string.h>

/*******************************************************************************
 * Function implementations
 ******************************************************************************/
void dhcp_buildBasicMessage(dhcp_message_t *pMsg,
                                 mac_address_t macAddress,
                                 uint32 sessionId)
{
  //Create a message for a DHCP request
  pMsg->op = 1;     //Message represents a request operation
  pMsg->htype = 1;  //We are using 10mb ethernet
  pMsg->hlen = 6;   //MAC addresses are 6 bytes long
  pMsg->hops = 0;   //Not sure exactly, RFC says clients set this to 0.

  utils_reverseBuffer((uint8*)&sessionId, 4);
  pMsg->xid = sessionId;
  
  //Assume no elapsed time
  pMsg->secs = 0;        
  
  uint16 flags = 0x8000;
  utils_reverseBuffer((uint8*)&flags, 2);
  pMsg->flags = flags;   //make sure the response is broadcast to us
  
  pMsg->ciaddr = 0;      // show that we don't have an IP address yet
  pMsg->yiaddr = 0;
  pMsg->siaddr = 0;
  pMsg->giaddr = 0;      //RFC says to set this to 0 if we're not 
                          //using relay agents.
  
  //Most of the other fields are meant to be set by the server in the response,
  //so don't touch those for now
  memcpy(pMsg->chaddr, &macAddress, 6);   //Set local hardware address
  pMsg->sname[0] = 0;                     //No server hostname
  pMsg->filename[0] = 0;                  //No boot filename
}
/******************************************************************************/
uint16 dhcp_buildDiscoverMessage(dhcp_message_t *pMsg,
                                 mac_address_t macAddress,
                                 uint32 sessionId)
{
  //Build out basic message.
  dhcp_buildBasicMessage(pMsg, macAddress, sessionId);
  
  //Add options.
  uint8 options[] = 
  {
    //Magic numbers required by RFC
    99, 130, 83, 99,  
    
    //DHCP message type option - DHCPDISCOVER
    DHCP_OPT_TYPE, 1, DHCPDISCOVER,  
    
    //Max message size option - 576 bytes
    DHCP_OPT_MAXSIZE, 2, 0x2, 0x40,  
    
    //Host name option - "CC2530"
    DHCP_OPT_HOSTNAME, 6, 0x43, 0x43, 0x32, 0x35, 0x33, 0x30,  
    
    //We'd like to get the subnet mask, gateway, DNS servers
    DHCP_OPT_REQPARAMS, 3, DHCP_OPT_SUBNET, DHCP_OPT_ROUTERS, DHCP_OPT_DNS
  };
  
  //Start with size of the basic options array.
  uint8 index = 24; 
  
  //Write out basic options
  memcpy(pMsg->options, options, index);      
  
  //Client ID (mac address)
  pMsg->options[index++] = DHCP_OPT_CLIENTID; 
  pMsg->options[index++] = 7;
  pMsg->options[index++] = 0x01;              //Ethernet MAC address
  pMsg->options[index++] = macAddress.b1;
  pMsg->options[index++] = macAddress.b2;
  pMsg->options[index++] = macAddress.b3;
  pMsg->options[index++] = macAddress.b4;
  pMsg->options[index++] = macAddress.b5;
  pMsg->options[index++] = macAddress.b6;
  
  //End of options.
  pMsg->options[index++] = DHCP_OPT_END;
  return(DHCP_HDR_SIZE + index);
}
/******************************************************************************/
uint16 dhcp_buildRequestMessage(dhcp_message_t *pMsg,
                                 mac_address_t macAddress,
                                 uint32 sessionId,
                                 ip_address_t ipAddress,
                                 uint32 serverId)
{    
  //Build out basic message.
  dhcp_buildBasicMessage(pMsg, macAddress, sessionId);
  
  //Add options.
  uint8 options[] = 
  {
    //Magic numbers required by RFC
    99, 130, 83, 99,  
    
    //DHCP message type option - DHCPDISCOVER
    DHCP_OPT_TYPE, 1, DHCPREQUEST,  
    
    //Max message size option - 576 bytes
    DHCP_OPT_MAXSIZE, 2, 0x2, 0x40,  
    
    //Host name option - "CC2530"
    DHCP_OPT_HOSTNAME, 6, 0x43, 0x43, 0x32, 0x35, 0x33, 0x30,  
    
    //We'd like to get the subnet mask, gateway, DNS servers
    DHCP_OPT_REQPARAMS, 3, DHCP_OPT_SUBNET, DHCP_OPT_ROUTERS, DHCP_OPT_DNS
  };
  
  //Start with size of the basic options array.
  uint8 index = 24; 
  
  //Write out basic options
  memcpy(pMsg->options, options, index);      
  
  //Client ID (mac address)
  pMsg->options[index++] = DHCP_OPT_CLIENTID; 
  pMsg->options[index++] = 7;
  pMsg->options[index++] = 0x01;              //Ethernet MAC address
  pMsg->options[index++] = macAddress.b1;
  pMsg->options[index++] = macAddress.b2;
  pMsg->options[index++] = macAddress.b3;
  pMsg->options[index++] = macAddress.b4;
  pMsg->options[index++] = macAddress.b5;
  pMsg->options[index++] = macAddress.b6;
  
  //Request IP option
  pMsg->options[index++] = DHCP_OPT_REQIP;    
  pMsg->options[index++] = 4;  
  //Write out the IP address
  pMsg->options[index++] = ipAddress.b1;
  pMsg->options[index++] = ipAddress.b2;
  pMsg->options[index++] = ipAddress.b3;
  pMsg->options[index++] = ipAddress.b4;  
//  network_writeDWord(ipAddress, (uint8*)&(pMsg->options[index]));
  index+=4;
  
  //Server ID option
  pMsg->options[index++] = DHCP_OPT_SERVERID; 
  pMsg->options[index++] = 4;
  //Write out the server Id
  network_writeDWord(serverId, (uint8*)&(pMsg->options[index]));
  index+=4;
  
  //End of options.
  pMsg->options[index++] = DHCP_OPT_END;
  
  return(DHCP_HDR_SIZE + index);
}
/******************************************************************************/
dhcp_option_value_t dhcp_getOption(dhcp_message_t *pMsg, uint16 messageSize,
                                                          uint8 option)
{
  dhcp_option_value_t optionInfo;
  
  //Set defaults on the return parameter
  optionInfo.pOption = NULL;
  optionInfo.size = 0;
  
  //Skip over the magic bytes at the beginning of the options field
  uint16 idx = 4;
  
  //Are additional fields used for options as well?
  uint8 *overload_start = 0;
  uint8 overload_len = 0;

  //Look at each option in the options field
  while (pMsg->options[idx] != DHCP_OPT_END 
              && idx < messageSize - DHCP_HDR_SIZE)
  {
    //Determine the current option
    uint8 curr_option = pMsg->options[idx++];
    
    //Is this what we are looking for?
    if (curr_option == option)
    {
      //If so, get the size of the option
      optionInfo.size = pMsg->options[idx++];
      
      //Store the location of the option.
      optionInfo.pOption = &(pMsg->options[idx++]);
      
      //Return from the function.
      return(optionInfo);
    }
    else if (curr_option == DHCP_OPT_OVERLOAD 
                            && pMsg->options[idx++] == 1) 
    {
      //Handle DHCP packets that extend their options into the
      //sname/file fields
      switch (pMsg->options[idx++])
      {
      case 1:
          overload_start = (uint8*)&(pMsg->filename);
          overload_len = 128;
          break;
      case 2:
          overload_start = (uint8*)&(pMsg->sname);
          overload_len = 64;
          break;
      case 3:
          overload_start = (uint8*)&(pMsg->sname);
          overload_len = 64 + 128;
      default:
          break;
      }
    }
    else if (curr_option != DHCP_OPT_PAD) 
    {
      //Otherwise, find the length of this option and skip over it
      uint8 len = pMsg->options[idx++];
      
      //TODO: Do a quick sanity check on the length first
      if (len > messageSize - DHCP_HDR_SIZE - idx)
      {
        return (optionInfo);
      }
      idx += len;
    }
  }

  //If required, search the additional fields now
  if (overload_len != 0) 
  {
    idx = 0;
    while (overload_start[idx] != DHCP_OPT_END && idx < overload_len)
    {
      uint8 curr_option = overload_start[idx++];
      
      //Is this the option we are looking for?
      if (curr_option == option) 
      {
        //If so, get the size of the option
        optionInfo.size = overload_start[idx++];
        
        //Store the location of the option.
        optionInfo.pOption = &(overload_start[idx]);
        
        //Return option Info.
        return(optionInfo);
      }
      else if (curr_option != DHCP_OPT_PAD) 
      {
        uint8 len = overload_start[idx++];
        if (len > overload_len - idx)
        {
          return (optionInfo);
        }
        idx += len;
      }
    }
  }
  return(optionInfo); 
}
/******************************************************************************/
uint8 dhcp_validateServerMessage(dhcp_message_t *pMsg, uint16 messageSize,
                                           uint32 sessionId, uint32 *pServerId)
{
  //Validate the message size.
  if(messageSize < DHCP_HDR_SIZE)
  {
    return(FAILED);
  }
  
  //Validate message operation.
  if(pMsg->op != 2)
  {
    return(FAILED);
  }
  
  //Validate session Id
  uint32 messageSessionId = pMsg->xid;
  utils_reverseBuffer((uint8*)&messageSessionId, 4);
  if(sessionId != messageSessionId)
  {
    return(FAILED);
  }
  
  //Validate server Id
  dhcp_option_value_t optionInfo = dhcp_getOption(pMsg, messageSize, 
                                                            DHCP_OPT_SERVERID);
  if(optionInfo.pOption != 0 && optionInfo.size == 4)
  {
    //Read the option into a 32 bit number
    uint32 messageServerId = network_readDWord(optionInfo.pOption);
    
    if(*pServerId != 0)
    {
      if(*pServerId != messageServerId)
      {
        return(FAILED);
      }
    }
    else
    {
      *pServerId = messageServerId;
    }
  }
  else
  {
    return(FAILED);
  }
  
  return(SUCCESS);
}